
//  Copyright (C) 2011-2012 Luca Piccioni
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;

namespace Derm.Render
{
	/// <summary>
	/// A set of RenderState.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class collects <see cref="RenderState"/> instances to specify a set of parameters that affect drawing
	/// operations. Each <see cref="RenderState"/> instance is applied, replacing the previous state having the same
	/// type (<see cref="IRenderState.Id"/>).
	/// </para>
	/// </remarks>
	public sealed class RenderStateSet
	{
		#region State Set Information

		/// <summary>
		/// An enumerable of the states collected by this RenderStateSet.
		/// </summary>
		public IEnumerable<RenderState> States
		{
			get
			{
				foreach (RenderState state in mRenderStates.Values)
					yield return state;
				foreach (RenderState state in mCustomStates)
					yield return state;
			}
		}

		/// <summary>
		/// The set of RenderState.
		/// </summary>
		private readonly Dictionary<string, RenderState> mRenderStates = new Dictionary<string, RenderState>();

		/// <summary>
		/// A list of custom states, applied after <see cref="mRenderStates"/>.
		/// </summary>
		private readonly List<RenderState> mCustomStates = new List<RenderState>();

		#endregion

		#region State Factory

		/// <summary>
		/// Factory method for getting the default render state set.
		/// </summary>
		/// <returns>
		/// It returns a RenderStateSet representing the default state set.
		/// </returns>
		internal static RenderStateSet GetDefaultSet()
		{
			RenderStateSet renderStateSet = new RenderStateSet();

			// Instantiate all context-bound states
			renderStateSet.DefineState(PolygonModeState.DefaultState);
			renderStateSet.DefineState(TransformState.DefaultState);
			renderStateSet.DefineState(BlendState.DefaultState);
			renderStateSet.DefineState(DepthTestState.DefaultState);
			renderStateSet.DefineState(CullFaceState.DefaultState);

			return (renderStateSet);
		}

		/// <summary>
		/// Factory method for getting the current render state set.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining the state vector.
		/// </param>
		/// <returns>
		/// It returns a RenderStateSet representing the currently active state vector.
		/// </returns>
		internal static RenderStateSet GetCurrentStateSet(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("not current", "ctx");

			RenderStateSet renderStateSet = new RenderStateSet();

			// Instantiate all context-bound states
			renderStateSet.DefineState(new BlendState(ctx));
			renderStateSet.DefineState(new DepthTestState(ctx));
			renderStateSet.DefineState(new CullFaceState(ctx));

			return (renderStateSet);
		}

		#endregion

		#region State Vector Definition

		/// <summary>
		/// Define/override a state.
		/// </summary>
		/// <param name="renderState">
		/// A <see cref="RenderState"/> that specify how the render state is modified.
		/// </param>
		public void DefineState(RenderState renderState)
		{
			if (renderState == null)
				throw new ArgumentNullException("renderState");

			// Store/override state
			if (renderState.Id != null)
				mRenderStates[renderState.Id] = renderState;
			else
				mCustomStates.Add(renderState);
		}

		/// <summary>
		/// Undefine a state.
		/// </summary>
		/// <param name="stateId">
		/// A <see cref="System.String"/> that identify a specific state to undefine.
		/// </param>
		public void UndefineState(string stateId)
		{
			if (stateId == null)
				throw new ArgumentNullException("stateId");

			mRenderStates.Remove(stateId);
		}

		/// <summary>
		/// Determine whether a specific state is defined in this set.
		/// </summary>
		/// <param name="stateId">
		/// A <see cref="System.String"/> that identify a specific state to undefine.
		/// </param>
		/// <returns></returns>
		public bool IsDefinedState(string stateId)
		{
			if (stateId == null)
				throw new ArgumentNullException("stateId");

			return (mRenderStates.ContainsKey(stateId));
		}

		/// <summary>
		/// Undefine all custom states.
		/// </summary>
		public void UndefineCustomStates()
		{
			mCustomStates.Clear();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="stateId"></param>
		/// <returns></returns>
		public RenderState this[string stateId]
		{
			get
			{
				RenderState state;

				if (mRenderStates.TryGetValue(stateId, out state) == true)
					return (state);

				return (null);
			}
		}

		#endregion

		#region State Vector Application

		/// <summary>
		/// Apply the set of RenderState collected by this instance.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining the state vector.
		/// </param>
		internal void Apply(RenderContext ctx)
		{
			Apply(ctx, null, null);
		}

		/// <summary>
		/// Apply the set of RenderState collected by this instance.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining the state vector.
		/// </param>
		/// <param name="program">
		/// A <see cref="ShaderProgram"/> defining the uniform state. This value can be null.
		/// </param>
		internal void Apply(RenderContext ctx, ShaderProgram program)
		{
			Apply(ctx, program, null);
		}

		/// <summary>
		/// Apply the set of RenderState collected by this instance.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining the state vector.
		/// </param>
		/// <param name="program">
		/// A <see cref="ShaderProgram"/> defining the uniform state.
		/// </param>
		/// <param name="currentStateSet">
		/// 
		/// </param>
		private void Apply(RenderContext ctx, ShaderProgram program, RenderStateSet currentStateSet)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			// Reset texture unit state
			if (program != null)
				program.ResetTextureUnits();

			// Apply known states
			foreach (KeyValuePair<string, RenderState> pair in mRenderStates) {
				RenderState state = pair.Value;

				if ((currentStateSet != null) && (currentStateSet.IsDefinedState(state.Id) == true)) {
					RenderState currentState = currentStateSet[state.Id];

					if (currentState.Inheritable == true) {
						
					}
				}

				if ((program != null) || (state.IsContextBound == true))
					state.ApplyState(ctx, program);
			}

			// Apply custom states, if any
			foreach (RenderState customState in mCustomStates) {
				if ((program != null) || (customState.IsContextBound == true))
					customState.ApplyState(ctx, program);
			}
		}

		/// <summary>
		/// Merge this RenderStateSet with a current state.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining the state vector.
		/// </param>
		/// <param name="program">
		/// A <see cref="ShaderProgram"/> defining the uniform state.
		/// </param>
		/// <param name="currentStateSet">
		/// 
		/// </param>
		/// <returns></returns>
		internal RenderStateSet Merge(RenderContext ctx, ShaderProgram program, RenderStateSet currentStateSet)
		{
			RenderStateSet mergedState = new RenderStateSet();

			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (currentStateSet == null)
				throw new ArgumentNullException("currentStateSet");

			// Apply most recent states
			foreach (KeyValuePair<string, RenderState> pair in mRenderStates)
				mergedState.DefineState((RenderState)pair.Value.Copy());
			// Keep inherited states
			foreach (KeyValuePair<string, RenderState> pair in currentStateSet.mRenderStates) {
				RenderState state = pair.Value;

				if (mergedState.IsDefinedState(state.Id) == false)
					mergedState.DefineState((RenderState)state.Copy());
				else
					mergedState[state.Id].Merge(ctx, state);
			}

			mergedState.Apply(ctx, program);

			return (mergedState);
		}

		#endregion

		#region ICloneable Implementation

		/// <summary>
		/// Clone this RenderStateSet.
		/// </summary>
		/// <returns>
		/// It returns a deep copy of this RenderStateSet.
		/// </returns>
		public RenderStateSet Copy()
		{
			RenderStateSet clone = new RenderStateSet();

			foreach (KeyValuePair<string, RenderState> pair in mRenderStates)
				clone.mRenderStates.Add(pair.Key, (RenderState)pair.Value.Copy());

			return (clone);
		}

		#endregion
	}
}
